﻿using AJWPFAdmin.Core.Components.ViewModels;
using AJWPFAdmin.Core.Components.Views;
using AJWPFAdmin.Core.Enums;
using AJWPFAdmin.Core.ExtensionMethods;
using AJWPFAdmin.Core.GlobalEvents;
using AJWPFAdmin.Core.HardwareSDKS.FFmpeg;
using AJWPFAdmin.Core.HardwareSDKS.VzClient;
using AJWPFAdmin.Core.Models;
using AJWPFAdmin.Core.Models.EF.Tables;
using AJWPFAdmin.Core.Utils;
using Masuit.Tools.Systems;
using Microsoft.EntityFrameworkCore;
using Org.BouncyCastle.Utilities.Encoders;
using Prism.Commands;
using SixLabors.ImageSharp.Drawing.Processing;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using Yitter.IdGenerator;

namespace AJWPFAdmin.Core.HardwareSDKS.Models
{
    public class VzCarIdentificationDevice : DeviceInfo
    {
        protected static readonly object _lock = new object();
        private CancellationTokenSource _videoCaptureExitCancelToken;


        private static readonly Dictionary<int, CarPlateColor> _vzClientCarPlateColorMaps = new Dictionary<int, CarPlateColor>
        {
            {0, CarPlateColor.其他 },
            {1, CarPlateColor.蓝牌 },
            {2, CarPlateColor.黄牌 },
            {3, CarPlateColor.白牌 },
            {4, CarPlateColor.黑牌 },
            {5, CarPlateColor.新能源绿牌},
            {6, CarPlateColor.新能源黄牌 },
        };

        private static readonly string[] _vzInvalidCarPlate = new string[] { "_无_", "无牌车" };

        public static readonly VzClientSDK.VZLPRC_PLATE_INFO_CALLBACK VZMSGCALLBACK = new VzClientSDK.VZLPRC_PLATE_INFO_CALLBACK(VzClientPlateResult);

        private static int VzClientPlateResult(int handle, IntPtr pUserData, IntPtr pResult, uint uNumPlates,
            VzClientSDK.VZ_LPRC_RESULT_TYPE eResultType, IntPtr pImgFull, IntPtr pImgPlateClip)
        {
            var result = (VzClientSDK.TH_PlateResult)Marshal.PtrToStructure(pResult, typeof(VzClientSDK.TH_PlateResult));
            var carNo = new string(result.license).TrimEnd('\0');

            if (string.IsNullOrWhiteSpace(carNo) || _vzInvalidCarPlate.Contains(carNo)) return 0;

            var strDecIP = new byte[32];
            VzClientSDK.VzLPRClient_GetDeviceIP(handle, ref strDecIP[0], 32);
            var strIP = Encoding.Default.GetString(strDecIP).TrimEnd('\0');

            var tempPath = Path.GetTempPath();
            var shortId = SnowFlake.GetInstance().GetUniqueShortId();
            var imageFile = Path.Combine(tempPath, $"full_{carNo}_{shortId}.jpg");
            var clipImageFile = Path.Combine(tempPath, $"clip_{carNo}_{shortId}.jpg");
            try
            {

                var saveRet = VzClientSDK.VzLPRClient_ImageSaveToJpeg(pImgFull, imageFile, 100);
                if (saveRet == -1)
                {
                    Logger?.Error($"臻识相机识别回调:{strIP} - {carNo} , 保存识别全图失败:{saveRet}");
                    imageFile = string.Empty;
                }
                saveRet = VzClientSDK.VzLPRClient_ImageSaveToJpeg(pImgPlateClip, clipImageFile, 100);
                if (saveRet == -1)
                {
                    clipImageFile = string.Empty;
                    Logger?.Error($"臻识相机识别回调:{strIP} - {carNo} , 保存识别车牌图失败:{saveRet}");
                }
            }
            catch (Exception e)
            {
                Logger?.Error($"臻识相机识别回调:{strIP} - {carNo} , 保存识别图片失败:{e.Message}", e);
                imageFile = clipImageFile = string.Empty;
            }

            Logger?.Info($"臻识相机识别回调:{strIP} - {carNo} , 发送车辆识别通知回调");

            EventAggregator.GetEvent<DeviceNotifyEvent>().Publish(new VzCarIdentificationDevice
            {
                IP = strIP,
                CarIdentificationPlateResult = new CarIdentificationResult
                {
                    CarNo = carNo,
                    Color = _vzClientCarPlateColorMaps[result.nColor],
                    FullImgFile = imageFile,
                    ClipImgFile = clipImageFile
                },
            });

            return 0;
        }

        /// <summary>
        /// LED文本模板配置,设备初始化的时候由DeviceScreenViewModel赋值, 第1位是空闲,第二位是正在通行, 第三位是已通过
        /// </summary>
        public string[] LEDTemplateTextArray { get; set; }

        private VzSocketClientSDK _sdk;

        private FFmpegRTSPCapture _videoCapture;

        private static readonly string[] _ledTextMap
            = new string[]
                    {
                        "00 64 FF FF 6E DL 00 04 ",
                        "00 01 01 05 00 R G 00 00 DL DATA",
                        "01 01 01 05 00 R G 00 00 DL DATA",
                        "02 01 01 05 00 R G 00 00 DL DATA",
                        "03 01 01 05 00 R G 00 00 DL DATA",
                        "0A VTL DATA"
                    };

        private static readonly string[] _ledColorReplaceArray = new string[] { "00", "FF" };

        private bool _videoRecoding;
        private System.Timers.Timer _videoRecordCheckTimer;

        private DelegateCommand<bool?> _toggleSwitchCmd;
        public DelegateCommand<bool?> ToggleSwitchCmd =>
            _toggleSwitchCmd ?? (_toggleSwitchCmd = new DelegateCommand<bool?>(ExecuteToggleSwitchCmd, CanExecuteToggleSwitchCmd));

        async void ExecuteToggleSwitchCmd(bool? isOpen)
        {
            var open = isOpen.GetValueOrDefault();
            if (open && EnableVideoCapture && VideoDuration > 0)
            {
                TriggerVideoCapture();
            }
            await ToggleGateSwitchCmdAsync(open);

            PlayDefaultTextToLED();
        }

        bool CanExecuteToggleSwitchCmd(bool? isOpen)
        {
            return !DeviceBusyOrError && !_videoRecoding;
        }


        private DelegateCommand _raiseCarPlatResultCmd;
        public DelegateCommand RaiseCarPlatResultCmd =>
            _raiseCarPlatResultCmd ?? (_raiseCarPlatResultCmd = new DelegateCommand(ExecuteRaiseCarPlatResultCmd));

        async void ExecuteRaiseCarPlatResultCmd()
        {
            await RaiseCarPlatResultAsync();
        }


        public override Task<bool> RaiseCarPlatResultAsync()
        {
            if (DeviceBusyOrError || _deviceHandle == 0)
            {
                return Task.FromResult(false);
            }
            try
            {
                return Task.FromResult(VzClientSDK.VzLPRClient_ForceTrigger(_deviceHandle) == 0);
            }
            catch (Exception e)
            {
                Logger?.Error($"{IP}-{Type}尝试强制触发车牌识别异常:{e.Message}", e);
            }
            return Task.FromResult(false);
        }

        public override async Task<bool> ToggleGateSwitchCmdAsync(bool open)
        {
            using var db = DbServiceBridgeHandle();

            var cfgJson = db.SystemConfigDictionaries
                    .Where(p => p.Key == SystemConfigKey.EntryConfig)
                    .Select(p => p.StringValue).FirstOrDefault();

            var cfg = CommonUtil.TryGetJSONObject<EntryConfig>(cfgJson) ?? new EntryConfig();

            if (DeviceBusyOrError || cfg.DisableGateControl)
            {
                return false;
            }

            if (!open && EnableIO2Alarm)
            {
                return true;
            }

            var delay = 500;

            var logOprTxt = open ? "开闸" : "关闸";

            var channel = open ? 0 : 1;

            var result = VzLPRClient_SetIOOutput(channel, 1, logOprTxt);

            await Task.Delay(delay);

            result = VzLPRClient_SetIOOutput(channel, 0, logOprTxt);

            return result;

            //PlayDefaultTextToLED();
        }

        private async Task<bool> ToggleIO2AlarmCmdAsync()
        {
            if (!EnableIO2Alarm)
            {
                return true;
            }

            var delay = IO2AlarmDelaySeconds;
            if (delay == 0)
            {
                delay = 5000;
            }
            var channel = 1;

            var oprTxt = "IO2报警";

            var result = VzLPRClient_SetIOOutput(channel, 1, oprTxt);

            await Task.Delay(delay);

            result = VzLPRClient_SetIOOutput(channel, 0, oprTxt);

            return result;
        }

        private bool VzLPRClient_SetIOOutput(int channel, int output, string logOprTxt = "SetIOOutput")
        {
            lock (_lock)
            {
                var result = false;
                try
                {
                    result = VzClientSDK.VzLPRClient_SetIOOutput(_deviceHandle, channel, output) == 0;
                    var success = result ? "成功" : "失败:接口返回-1";
                    Logger?.Info($"{IP}-{Type}尝试{logOprTxt}{success}:参数 uChnId {channel}; nOutput {output}；");
                }
                catch (Exception e)
                {
                    Logger?.Error($"{IP}-{Type}尝试{logOprTxt}异常:参数 uChnId {channel}; nOutput {output}", e);
                }
                return result;
            }
            
        }

        public override void Open()
        {
            try
            {
                _videoRecordCheckTimer = new System.Timers.Timer
                {
                    Interval = TimeSpan.FromMinutes(1).TotalMilliseconds,
                    AutoReset = false,
                };

                _videoRecordCheckTimer.Elapsed += VideoRecordCheckTimer_Elapsed;

                _ctrl.Dispatcher.Invoke(() => SetBusyOrError("加载中..."));

                RTSPUrl = $"rtsp://{IP}:8557/sub_stream_1";
                StartVideo();

                try
                {
                    _deviceHandle = VzClientSDK.VzLPRClient_Open(IP, (ushort)Port, LoginName, LoginPassword);
                    _serialPortHandle = VzClientSDK.VzLPRClient_SerialStart(_deviceHandle, 0, null, IntPtr.Zero);

                    if (_deviceHandle != 0 && EnableVideoCapture && VideoDuration > 0)
                    {
                        _videoCaptureExitCancelToken = new CancellationTokenSource();

                        Task.Factory.StartNew(MonitorVideoCaptuerINValue, TaskCreationOptions.LongRunning);

                        if (EnableIO2Alarm)
                        {
                            VzLPRClient_SetIOOutput(1, 0, "IO2报警");
                        }
                    }

                    PlayDefaultTextToLED();
                }
                catch (Exception e)
                {
                    _ctrl.Dispatcher.Invoke(() => SetBusyOrError($"登录失败:{e.Message}"));
                    Logger.Error($"{Type}:LPRClientOpen控制失败:{e.Message}");
                }

                if (!OnlyMonitor && _deviceHandle != 0)
                {
                    try
                    {
                        var ret = VzClientSDK.VzLPRClient_SetPlateInfoCallBack(_deviceHandle, VZMSGCALLBACK, IntPtr.Zero, 1);
                        if (ret == -1)
                        {
                            _ctrl.Dispatcher.Invoke(() => SetBusyOrError($"布防失败:{ret}"));
                        }
                    }
                    catch (Exception e)
                    {
                        _ctrl.Dispatcher.Invoke(() => SetBusyOrError($"设置车牌识别失败:{e.Message}"));
                    }
                }

            }
            catch (Exception e)
            {
                Logger.Error($"{Type} 设备登录初始化失败:name:{LoginName},psw:{LoginPassword},ip:{IP},port:{Port}", e);
                _ctrl.Dispatcher.Invoke(() => SetBusyOrError("加载错误"));
            }

        }

        private void VideoRecordCheckTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            _videoRecordCheckTimer.Stop();
            _videoRecoding = false;
            ToggleSwitchCmd?.RaiseCanExecuteChanged();
        }

        private void MonitorVideoCaptuerINValue()
        {
            while (true)
            {
                if (_videoCaptureExitCancelToken.IsCancellationRequested)
                {
                    break;
                }

                var gpIOStateList = new List<int>();

                var tempGPIOStatus = new int[1];
                var _tempGPIOHandleObject = GCHandle.Alloc(tempGPIOStatus, GCHandleType.Pinned);
                var _tempGPIOHandlePtr = _tempGPIOHandleObject.AddrOfPinnedObject();

                VzClientSDK.VzLPRClient_GetGPIOValue(_deviceHandle, 0, _tempGPIOHandlePtr);

                if (tempGPIOStatus[0] == AutoVideoCaptureINValue.TryGetInt())
                {
                    TriggerVideoCapture();
                }

                _tempGPIOHandleObject.Free();

                Thread.Sleep(5000);
            }
        }

        private void TriggerVideoCapture()
        {
            try
            {
                using var db = DbServiceBridgeHandle();
                var wid = WatchhouseId;
                var watchHouse = db.Watchhouses.AsNoTracking().Where(p => p.Id == wid).FirstOrDefault();

                var pid = PassagewayId;
                var passageway = db.Passageways.AsNoTracking().Where(p => p.Id == pid).FirstOrDefault();

                var did = Id;
                var device = db.Devices.AsNoTracking().Where(p => p.Id == did).FirstOrDefault();

                var now = DateTime.Now;
                var recordId = YitIdHelper.NextId();

                var cfgJson = db.SystemConfigDictionaries
                    .Where(p => p.Key == SystemConfigKey.CarIdentificationConfig)
                    .Select(p => p.StringValue).FirstOrDefault();

                var carIdentityCfg = CommonUtil.TryGetJSONObject<CarIdentificationConfig>(cfgJson)
                    ?? new CarIdentificationConfig() { Type = CarIdentificationConfig.StoreType.本地 };

                var record = new Core.Models.EF.Tables.ManualPassRecord
                {
                    Id = recordId,
                    CreateDate = now,
                    UpdateDate = now,
                    WatchhouseId = WatchhouseId,
                    WatchhouseCode = watchHouse?.Code,
                    WatchhouseName = watchHouse?.Name,
                    PassagewayId = PassagewayId,
                    PassagewayCode = passageway?.Code,
                    PassagewayName = passageway.Name,
                    DeviceId = Id,
                    DeviceCode = Code,
                    DeviceName = device?.Name,
                    Direction = Direction
                };
                record.ComputeVideoCaptureDir(carIdentityCfg);
                db.ManualPassRecords.Add(record);

                db.SaveChanges();

                _deviceReceiveCmdEvent.Publish(new DeviceReceiveCmdEventArgs
                {
                    DeviceId = record.DeviceId,
                    PassagewayId = record.PassagewayId,
                    Type = DeviceReceiveCmdType.开闸,
                    Data = new DeviceOpenGateCmdParameter
                    {
                        Direction = record.Direction,
                        Open = true,
                        TTSText = $"允许通行",
                        LEDTextLines = new string[] { }
                    }
                });

                if (_videoRecordCheckTimer.Enabled)
                {
                    _videoRecordCheckTimer.Stop();
                }
                _videoRecoding = true;
                ToggleSwitchCmd?.RaiseCanExecuteChanged();
                _videoRecordCheckTimer.Start();

                _deviceReceiveCmdEvent.Publish(new DeviceReceiveCmdEventArgs
                {
                    DeviceId = Id,
                    PassagewayId = PassagewayId,
                    Type = DeviceReceiveCmdType.录像,
                    Data = new DeviceVideoCaptureCmdParameter
                    {
                        VideoDuration = VideoDuration.GetValueOrDefault(),
                        RecordId = record.Id,
                        StoreDirectory = record.VideoStoreDirectory
                    }
                });

                Logger?.Debug($"{IP} {Type}触发录像事件参数:设备id:{Id};道闸id:{PassagewayId};记录id:{record.Id};时长:{VideoDuration};位置:{record.VideoStoreDirectory}");
            }
            catch (Exception e)
            {
                Logger?.Error($"{IP} {Type}触发录像失败:{e.Message}", e);
            }
        }

        public override void Close()
        {
            //_sdk.ErrorEvent -= OnSDKErrorEvent;
            //_sdk.RtspUriResponseEvent -= OnSDKRTSPEvent;
            //_sdk.IVSCarPlateResultEvent -= OnIVSCarPlateResultEvent;
            //_sdk.Shutdown();

            _videoRecordCheckTimer.Elapsed -= VideoRecordCheckTimer_Elapsed;
            _videoRecordCheckTimer.Stop();
            _videoRecordCheckTimer.Dispose();

            _videoCaptureExitCancelToken?.Cancel();

            ExitVideo();

            try
            {
                VzClientSDK.VzLPRClient_Close(_deviceHandle);
            }
            catch
            {
            }

        }

        private void OnSDKErrorEvent(object sender, VzSDKErrorEventArgs e)
        {
            if (e.NotifyToUser)
            {
                _ctrl.Dispatcher.Invoke(() =>
                {
                    AJCommonDialog.ShowDialog(new AJCommonDialogViewModel.AJCommonDialogParams
                    {
                        Title = $"设备:{Code}错误",
                        Message = $"{DeviceDescription} : {e.ErrMessage}"
                    });
                });
                return;
            }
            SetBusyOrError(e.ErrMessage);
            Logger.Error($"{DeviceDescription}:发生错误:{e.ErrMessage}", e.ErrorData);

        }

        protected override async void OnDeviceReceiveCmdEvent(DeviceReceiveCmdEventArgs e)
        {
            switch (e.Type)
            {
                case DeviceReceiveCmdType.开闸:
                    if (e.DeviceId != Id)
                    {
                        return;
                    }
                    Logger?.Info($"收到开闸事件通知,执行开闸:设备id:{e.DeviceId}");
                    ExecuteOpenGateCmd((DeviceOpenGateCmdParameter)e.Data);
                    break;
                case DeviceReceiveCmdType.IO2报警:
                    if (e.DeviceId != Id)
                    {
                        return;
                    }
                    Logger?.Info($"收到IO2报警通知,执行报警:设备id:{e.DeviceId}");
                    await ToggleIO2AlarmCmdAsync();
                    break;
                //case DeviceReceiveCmdType.录像:
                //    var captureParam = (DeviceVideoCaptureCmdParameter)e.Data;
                //    if (PassagewayId != e.PassagewayId)
                //    {
                //        return;
                //    }
                //    new HIKVisionDevice().StartVideoCapture(captureParam);
                //    break;

                default:
                    break;
            }
        }

        private async void ExecuteOpenGateCmd(DeviceOpenGateCmdParameter data)
        {
            if ((data?.Open).GetValueOrDefault())
            {
                Logger?.Debug($"{IP}-{Type} 执行开闸。");
                await ToggleGateSwitchCmdAsync(true);
            }

            if (data != null)
            {
                data.PreProcessLEDTextLines();

                PlayTextToDaoTongWuLianLED(data.TTSText, "g", data.LEDTextLines);
            }

        }

        private void PlayDefaultTextToLED()
        {
            var idleTpl = LEDTemplateTextArray?.ElementAtOrDefault(0) ?? string.Empty;
            var @params = new DeviceOpenGateCmdParameter { LEDTextLines = idleTpl.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries) };

            @params.PreProcessLEDTextLines();

            PlayTextToDaoTongWuLianLED(string.Empty, "g", @params.LEDTextLines);
        }


        private void PlayTextToDaoTongWuLianLED(string tts, string rgb, params string[] lines)
        {
            var line1 = LEDTextToHEXString(CleanAndPrepareLEDText(lines.ElementAtOrDefault(0)));
            var line2 = LEDTextToHEXString(CleanAndPrepareLEDText(lines.ElementAtOrDefault(1)));
            var line3 = LEDTextToHEXString(CleanAndPrepareLEDText(lines.ElementAtOrDefault(2)));
            var line4 = LEDTextToHEXString(CleanAndPrepareLEDText(lines.ElementAtOrDefault(3)));

            tts = LEDTextToHEXString(tts);

            var line1Len = ComputeLEDHEXTextLength(line1);
            var line2Len = ComputeLEDHEXTextLength(line2);
            var line3Len = ComputeLEDHEXTextLength(line3);
            var line4Len = ComputeLEDHEXTextLength(line4);
            var ttsLen = ComputeLEDHEXTextLength(tts);

            var formatHead = _ledTextMap.ElementAtOrDefault(0);
            var formatLine1 = _ledTextMap.ElementAtOrDefault(1);
            var formatLine2 = _ledTextMap.ElementAtOrDefault(2);
            var formatLine3 = _ledTextMap.ElementAtOrDefault(3);
            var formatLine4 = _ledTextMap.ElementAtOrDefault(4);
            var formatTTS = _ledTextMap.ElementAtOrDefault(5);

            var replaceStr = rgb == "g" ? _ledColorReplaceArray : _ledColorReplaceArray.Reverse().ToArray();

            formatLine1 = formatLine1.Replace("R", replaceStr[0]).Replace("G", replaceStr[1]);
            formatLine2 = formatLine2.Replace("R", replaceStr[0]).Replace("G", replaceStr[1]);
            formatLine3 = formatLine3.Replace("R", replaceStr[0]).Replace("G", replaceStr[1]);
            formatLine4 = formatLine4.Replace("R", replaceStr[0]).Replace("G", replaceStr[1]);


            formatLine1 = formatLine1.Replace("DL", line1Len).Replace("DATA", line1);
            formatLine2 = formatLine2.Replace("DL", line2Len).Replace("DATA", line2);
            formatLine3 = formatLine3.Replace("DL", line3Len).Replace("DATA", line3);
            formatLine4 = formatLine4.Replace("DL", line4Len).Replace("DATA", line4);
            formatTTS = formatTTS.Replace("VTL", ttsLen).Replace("DATA", tts);

            var data = formatLine1 + "0D " + formatLine2 + "0D " + formatLine3 + "0D " + formatLine4 + "00 " + formatTTS + "00 ";
            var dataLen = ComputeLEDHEXTextLength(data, 2);

            formatHead = formatHead.Replace("DL", dataLen);
            data = formatHead + data;
            string strcrc = CRC.ToModbusCRC16(data, true);
            strcrc = strcrc.Insert(2, " ");
            data = data + strcrc;

            byte[] send_buf = new byte[1024];
            string new_content = data.Insert(data.Length, " ");
            int txt_len = new_content.Length;
            char[] txt_buf = new_content.ToCharArray();

            int index = 0;

            char[] strHex = new char[3];
            byte uc;
            for (int i = 0; i < txt_len - 2; i += 3)
            {
                if (txt_buf[i + 2] != ' ')
                {
                    Logger.Info($"{DeviceDescription}:LED发送消息失败: 16进制数据输入格式不正确 :{new string(txt_buf)}");
                    return;
                }

                strHex[0] = txt_buf[i];
                strHex[1] = txt_buf[i + 1];
                strHex[2] = (char)0;

                for (int j = 0; j < 2; j++)
                {
                    if (strHex[j] < '0' || (strHex[j] > '9' && strHex[j] < 'A') || (strHex[j] > 'F' &&
                        strHex[j] < 'a') || strHex[j] > 'f')
                    {
                        Logger.Info($"{DeviceDescription}:LED发送消息失败: 16进制数据输入格式不正确 :{new string(strHex)}");
                        return;
                    }
                }

                string hex_value = new string(strHex);
                uc = byte.Parse(hex_value, System.Globalization.NumberStyles.HexNumber);
                send_buf[index] = uc;
                index++;
            }

            try
            {
                var hObject = GCHandle.Alloc(send_buf, GCHandleType.Pinned);
                var pObject = hObject.AddrOfPinnedObject();

                VzClientSDK.VzLPRClient_SerialSend(_serialPortHandle, pObject, index);

                if (hObject.IsAllocated)
                    hObject.Free();
            }
            catch (Exception e)
            {
                Logger.Error($"{DeviceDescription}:LED发送消息失败: {e.Message}");
            }

        }

        /// <summary>
        /// 清除文字换行和空格
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private string CleanAndPrepareLEDText(string text)
        {
            if (string.IsNullOrWhiteSpace(text))
            {
                return " ";
            }

            return text.Replace(" ", string.Empty)
                .Replace("\0", string.Empty)
                .Replace("\r\n", string.Empty).Replace("\n", string.Empty).Trim();

        }

        /// <summary>
        /// 将LED文字转换成HEX String
        /// </summary>
        /// <param name="text"></param>
        /// <returns></returns>
        private string LEDTextToHEXString(string text)
        {
            var result = string.Empty;
            var arrByte = Encoding.GetEncoding("GB2312").GetBytes(text);
            for (int i = 0; i < arrByte.Length; i++)
            {
                result += Convert.ToString(arrByte[i], 16) + " ";
            }

            return result.ToUpper();
        }

        private string ComputeLEDHEXTextLength(string text, int extra = 0)
        {
            return Convert.ToString(text.Length / 3 + extra, 16).ToUpper().PadLeft(2, '0');
        }
    }
}
